﻿using System.Net;
using System.Net.Sockets;

using Aliphant.Common;

using Common.NetWork;

using GRPCDemo.Protocol;

namespace Aliphant.Net.Client.Network
{
    public class NetClient : Singleton<NetClient>
    {
        public const int NET_ERROR_ZERO_BYTE = 1002;            //收发0字节


        private Socket? clientSocket;
        private bool isConnected = false;
        private MemoryStream sendBuffer = new MemoryStream();
        private MemoryStream receiveBuffer = new MemoryStream(64 * 1024);
        private int sendOffset = 0;

        private Queue<NetMessage> sendQueue = new Queue<NetMessage>();
        public PackageHelper packageHandler = new PackageHelper(null);

        private IPEndPoint? remoteEndPoint;

        public bool IsConnected => isConnected;

        public void Connected(string ip, string port)
        {
            remoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
            if (isConnected)
            {
                CloseConnection();
            }
            DoConnected();
        }

        public void CloseConnection()
        {
            if (clientSocket != null)
            {
                if (sendQueue.Count > 0)
                {
                    while (sendQueue.Count > 0)
                    {
                        Thread.Sleep(0);
                    }
                }

                clientSocket.Close();
            }
            this.receiveBuffer.Position = 0;
            this.sendBuffer.Position = sendOffset = 0;
            isConnected = false;
        }


        public void SendMessage(NetMessage message)
        {
            if (!isConnected)
            {
                DoConnected();
            }

            if (isConnected)
            {
                this.receiveBuffer.Position = 0;
                this.sendBuffer.Position = sendOffset = 0;

                sendQueue.Enqueue(message);
            }
        }

        private void DoConnected()
        {
            if (isConnected)
            {
                return;
            }

            if (remoteEndPoint == null)
            {
                return;
            }

            var address = remoteEndPoint;
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //Log("DoConnect on 127.0.0.1:8008...");
            try
            {
                clientSocket.Blocking = true;
                IAsyncResult result = clientSocket.BeginConnect(address, null, null);
                bool success = result.AsyncWaitHandle.WaitOne(10000);
                if (success)
                {
                    clientSocket.EndConnect(result);
                }
            }
            catch (Exception ex)
            {
                Logger.Error("DoConnect Exception:" + ex.ToString() + "\n");
            }

            if (clientSocket.Connected)
            {
                clientSocket.Blocking = false;
                isConnected = true;
            }
        }

        /// <summary>
        /// 处理接收
        /// </summary>
        /// <returns></returns>
        private bool ProcessRecv()
        {
            bool ret = false;
            try
            {
                if (this.clientSocket == null)
                {
                    return false;
                }

                if (this.clientSocket.Blocking)
                {
                    Logger.Info("this.clientSocket.Blocking = true\n");
                }
                bool error = this.clientSocket.Poll(0, SelectMode.SelectError);
                if (error)
                {
                    Logger.Error("ProcessRecv Poll SelectError\n");
                    this.CloseConnection();
                    return false;
                }

                ret = this.clientSocket.Poll(0, SelectMode.SelectRead);
                if (ret)
                {
                    int n = this.clientSocket.Receive(this.receiveBuffer.GetBuffer(), 0, this.receiveBuffer.Capacity, SocketFlags.None);
                    if (n <= 0)
                    {
                        this.CloseConnection();
                        return false;
                    }

                    this.packageHandler.ReceiveData(this.receiveBuffer.GetBuffer(), 0, n);

                }
            }
            catch (Exception e)
            {
                Logger.Error("ProcessReceive exception:" + e.ToString() + "\n");
                this.CloseConnection();
                return false;
            }
            return true;
        }

        private bool ProcessSend()
        {
            bool ret = false;
            try
            {
                if (this.clientSocket == null)
                {
                    return false;
                }
                if (this.clientSocket.Blocking)
                {
                    //Log("this.clientSocket.Blocking = true\n");
                }
                bool error = this.clientSocket.Poll(0, SelectMode.SelectError);
                if (error)
                {
                    Logger.Error("ProcessSend Poll SelectError\n");
                    this.CloseConnection();
                    return false;
                }
                ret = this.clientSocket.Poll(0, SelectMode.SelectWrite);
                if (ret)
                {
                    //sendStream exist data
                    if (this.sendBuffer.Position > this.sendOffset)
                    {
                        int bufsize = (int)(this.sendBuffer.Position - this.sendOffset);
                        int n = this.clientSocket.Send(this.sendBuffer.GetBuffer(), this.sendOffset, bufsize, SocketFlags.None);
                        if (n <= 0)
                        {
                            this.CloseConnection();
                            return false;
                        }
                        this.sendOffset += n;
                        if (this.sendOffset >= this.sendBuffer.Position)
                        {
                            this.sendOffset = 0;
                            this.sendBuffer.Position = 0;
                            this.sendQueue.Dequeue();//remove message when send complete
                        }
                    }
                    else
                    {
                        //fetch package from sendQueue
                        if (this.sendQueue.Count > 0)
                        {
                            NetMessage message = this.sendQueue.Peek();
                            byte[] package = PackageHelper.PackMessage(message);

                            //var msg = PackageHelper.UnPackMessage(package, 0, package.Length);

                            this.sendBuffer.Write(package, 0, package.Length);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Error("ProcessSend exception:" + e.ToString() + "\n");
                this.CloseConnection();
                return false;
            }

            return true;
        }

        private void ProcessMessage()
        {
            MessageDistributer.Instance.Distribute();
        }

        public void Update()
        {
            if (isConnected)
            {
                if (ProcessRecv())
                {
                    if (isConnected)
                    {
                        ProcessSend();
                        ProcessMessage();
                    }
                }
            }
            else
            {
                DoConnected();
            }
        }
    }
}
